package com.smart.milvus.conditions;

import com.smart.milvus.bean.MilvusModel;
import com.smart.milvus.exception.MilvusException;
import com.smart.milvus.conditions.segments.ColumnSegment;
import com.smart.milvus.enums.ExprKeyword;
import com.smart.milvus.enums.ExprLike;
import com.smart.milvus.model.metadata.CollectionHelper;
import com.smart.milvus.util.Assert;
import com.smart.milvus.util.LambdaUtils;
import com.smart.milvus.util.ExprUtils;
import com.smart.milvus.util.PropertyNamer;
import com.smart.milvus.util.support.ColumnCache;
import com.smart.milvus.base.func.SFunction;
import com.smart.milvus.util.support.LambdaMeta;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static com.smart.milvus.enums.ExprKeyword.*;

public abstract class AbstractLambdaWrapper<T extends MilvusModel<? extends MilvusModel<?>>, C extends AbstractLambdaWrapper<T, C>>
        extends AbstractWrapper<T, SFunction<T, ?>, C> implements ChildrenWrapper<C, T> {

    private final Map<String, ColumnCache> columnMap = new ConcurrentHashMap<>();


    @Override
    protected String columnToString(SFunction<T, ?> column) {
        ColumnCache cache = getColumnCache(column);
        return cache.getColumn();
    }

    protected String columnToStringWithoutType(SFunction<?, ?> column) {
        ColumnCache cache = getColumnCache(column);
        return cache.getColumn();
    }


    /**
     * 获取 SerializedLambda 对应的列信息，从 lambda 表达式中推测实体类
     * <p>
     * 如果获取不到列信息，那么本次条件组装将会失败
     *
     * @param column 列
     * @return 列
     * @throws MilvusException 获取不到列信息时抛出异常
     */
    protected ColumnCache getColumnCache(SFunction<?, ?> column) {
        LambdaMeta meta = LambdaUtils.extract(column);
        String fieldName = PropertyNamer.methodToProperty(meta.getImplMethodName());
        Class<?> instantiatedClass = meta.getInstantiatedClass();
        tryInitCache(instantiatedClass);
        return getColumnCache(fieldName, instantiatedClass);
    }

    private void tryInitCache(Class<?> lambdaClass) {
        Map<String, ColumnCache> cacheMap = LambdaUtils.getColumnMap(lambdaClass);
        if (cacheMap == null) {
            CollectionHelper.initCollectionInfo(lambdaClass);
            cacheMap = LambdaUtils.getColumnMap(lambdaClass);
        }
        columnMap.putAll(cacheMap);
    }

    private ColumnCache getColumnCache(String fieldName, Class<?> lambdaClass) {
        ColumnCache columnCache = columnMap.get(LambdaUtils.formatKey(fieldName));
        Assert.notNull(columnCache, "can not find lambda cache for this property [%s] of entity [%s]",
                fieldName, lambdaClass.getName());
        return columnCache;
    }

    protected final <M> ColumnSegment columnToExprSegment(SFunction<T, M> column1, SFunction<M, ?> column2) {
        return () -> columnToStringWithoutType(column1, column2);
    }

    protected <M> String columnToStringWithoutType(SFunction<T, M> column1, SFunction<M, ?> column2) {
        ColumnCache cache = getColumnCache(column1);
        StringBuilder mainColumn = new StringBuilder(cache.getColumn());
        mainColumn.append("['").append(columnToStringWithoutType(column2)).append("']");
        return mainColumn.toString();
    }

    protected <M> C addCondition(boolean condition, SFunction<T, M> column1, SFunction<M, ?> column2, ExprKeyword exprKeyword, Object val) {
        return maybeDo(condition, () -> appendExprSegments(columnToExprSegment(column1, column2), exprKeyword,
                () -> formatParam(val)));
    }

    protected <M> C likeValue(boolean condition, ExprKeyword keyword, SFunction<T, M> column1, SFunction<M, ?> column2, Object val, ExprLike exprLike) {
        return maybeDo(condition, () -> appendExprSegments(columnToExprSegment(column1, column2), keyword,
                () -> ExprUtils.concatLike(val, exprLike)));
    }


    @Override
    public <M> C eq(boolean condition, SFunction<T, M> column1, SFunction<M, ?> column2, Object val) {
        return addCondition(condition, column1, column2, EQ, val);
    }

    @Override
    public <M> C ne(boolean condition, SFunction<T, M> column1, SFunction<M, ?> column2, Object val) {
        return addCondition(condition, column1, column2, NE, val);
    }

    @Override
    public <M> C gt(boolean condition, SFunction<T, M> column1, SFunction<M, ?> column2, Object val) {
        return addCondition(condition, column1, column2, GT, val);
    }

    @Override
    public <M> C ge(boolean condition, SFunction<T, M> column1, SFunction<M, ?> column2, Object val) {
        return addCondition(condition, column1, column2, GE, val);
    }

    @Override
    public <M> C lt(boolean condition, SFunction<T, M> column1, SFunction<M, ?> column2, Object val) {
        return addCondition(condition, column1, column2, LT, val);
    }

    @Override
    public <M> C le(boolean condition, SFunction<T, M> column1, SFunction<M, ?> column2, Object val) {
        return addCondition(condition, column1, column2, LE, val);
    }

    @Override
    public <M> C notLikeRight(boolean condition, SFunction<T, M> column1, SFunction<M, ?> column2, Object val) {
        return likeValue(condition, NOT_LIKE, column1, column2, val, ExprLike.RIGHT);
    }

    @Override
    public <M> C likeRight(boolean condition, SFunction<T, M> column1, SFunction<M, ?> column2, Object val) {
        return likeValue(condition, LIKE, column1, column2, val, ExprLike.RIGHT);
    }

    @Override
    public <M> C in(boolean condition, SFunction<T, M> column1, SFunction<M, ?> column2, Collection coll) {
        return maybeDo(condition, () -> appendExprSegments(columnToExprSegment(column1, column2), IN, inExpression(coll)));
    }

    @Override
    public <M> C notIn(boolean condition, SFunction<T, M> column1, SFunction<M, ?> column2, Collection coll) {
        return maybeDo(condition, () -> appendExprSegments(columnToExprSegment(column1, column2), NOT_IN, inExpression(coll)));
    }

    @Override
    public <M> C jsonContains(boolean condition, SFunction<T, M> column1, SFunction<M, ?> column2, Object value) {
        return maybeDo(condition, () -> appendExprSegments(JSON, jsonExpression(columnToStringWithoutType(column1, column2), value)));
    }

    @Override
    public <M> C jsonContainsAll(boolean condition, SFunction<T, M> column1, SFunction<M, ?> column2, Collection coll) {
        return maybeDo(condition, () -> appendExprSegments(JSON_ALL, jsonExpression(columnToStringWithoutType(column1, column2), coll)));
    }

    @Override
    public <M> C jsonContainsAny(boolean condition, SFunction<T, M> column1, SFunction<M, ?> column2, Collection coll) {
        return maybeDo(condition, () -> appendExprSegments(JSON_ANY, jsonExpression(columnToStringWithoutType(column1, column2), coll)));
    }
}
